﻿/*
Author : Onur "Xtro" ER
Create date : 12.05.2009
Last update : 18.12.2011
*/

using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using Xtro.MDX.DXGI;
using Xtro.MDX.Direct3D10;
using Device = Xtro.MDX.Direct3D10.Device;
using DXGI_Error = Xtro.MDX.DXGI.Error;
using D3D10Error = Xtro.MDX.Direct3D10.Error;
using D3DX10Error = Xtro.MDX.Direct3DX10.Error;
using DXGI_Functions = Xtro.MDX.DXGI.Functions;
using D3D10Functions = Xtro.MDX.Direct3D10.Functions;
using ErrorHandlingFunctions = Xtro.MDX.ErrorHandling.Functions;

using Xtro.Gerecler;

namespace Xtro.DirectX.Direct3D
{
    /// <summary>
    /// Provides static methods and properties to manage a Direct3D application. This class cannot be inherited.
    /// </summary>
    // ReSharper disable InconsistentNaming
    public static class Manager
    // ReSharper restore InconsistentNaming
    {
        internal static readonly Type Type = MethodBase.GetCurrentMethod().ReflectedType;
        internal static Factory Factory { get; private set; }
        internal static readonly List<TAdapter> FAdapters = new List<TAdapter>();
        internal static readonly List<TContext> Contexts = new List<TContext>();

        public sealed class TDisplayMode
        {
            internal readonly List<Rational> FRefreshRates = new List<Rational>();

            internal TDisplayMode(uint Width, uint Height, Rational RefreshRate)
            {
                this.Width = Width;
                this.Height = Height;
                RefreshRates = FRefreshRates.AsReadOnly();

                FRefreshRates.Add(RefreshRate);
            }

            public readonly uint Width;
            public readonly uint Height;
            public readonly ReadOnlyCollection<Rational> RefreshRates;
        }

        public sealed class TMonitor
        {
            internal Output Output { get; private set; }

            internal readonly List<TDisplayMode> FDisplayModes = new List<TDisplayMode>();

            internal TMonitor(Output Output)
            {
                this.Output = Output;
                var Result = Output.GetDescription(out Description);
                if (Result != 0) throw new EDirectX("Output.GetDescription", Result, "Monitor");

                #region Display modes

                DisplayModes = FDisplayModes.AsReadOnly();

                var ModeCount = 0u;
                Result = Output.GetDisplayModeList(Format.R8G8B8A8_UNorm, EnumerationModeFlag.Scaling, ref ModeCount, null);
                if (Result != 0) throw new EDirectX("Output.GetDisplayModeList", Result, "Monitor");

                var RawDisplayModeList = new ModeDescription[ModeCount];
                Result = Output.GetDisplayModeList(Format.R8G8B8A8_UNorm, EnumerationModeFlag.Scaling, ref ModeCount, RawDisplayModeList);
                if (Result != 0) throw new EDirectX("Output.GetDisplayModeList", Result, "Monitor");

                foreach (var Mode in RawDisplayModeList)
                {
                    var DisplayModeAddedBefore = FDisplayModes.Find(DisplayMode => (DisplayMode.Width == Mode.Width) && (DisplayMode.Height == Mode.Height));

                    if (DisplayModeAddedBefore == null) FDisplayModes.Add(new TDisplayMode(Mode.Width, Mode.Height, Mode.RefreshRate));
                    else
                    {
                        var ModeHz = RationalToFloat(Mode.RefreshRate).ToString("0");

                        var RefreshRateAddedBefore = false;
                        foreach (var RefreshRate in DisplayModeAddedBefore.RefreshRates)
                        {
                            RefreshRateAddedBefore = ModeHz == RationalToFloat(RefreshRate).ToString("0");
                            if (RefreshRateAddedBefore) break;
                        }
                        if (RefreshRateAddedBefore) continue;

                        DisplayModeAddedBefore.FRefreshRates.Add(Mode.RefreshRate);
                    }
                }

                #endregion
            }

            internal void Delete()
            {
                Output.Release();
                Output = null;
            }

            public readonly ReadOnlyCollection<TDisplayMode> DisplayModes;
            public readonly OutputDescription Description;
        }

        public sealed class TAdapter
        {
            internal Adapter Adapter { get; private set; }

            internal readonly List<TMonitor> FMonitors = new List<TMonitor>();
            internal readonly List<SampleDescription> FSamplings = new List<SampleDescription>();

            internal TAdapter(Adapter Adapter)
            {
                this.Adapter = Adapter;
                var Result = Adapter.GetDescription(out Description);
                if (Result != 0) throw new EDirectX("Adapter.GetDescription", Result, "Adapter");

                // Monitors

                Monitors = FMonitors.AsReadOnly();

                var I = 0u;
                Output Output;
                while (Adapter.EnumerateOutputs(I, out Output) != (int)DXGI_Error.NotFound)
                {
                    try { FMonitors.Add(new TMonitor(Output)); }
                    catch (EDirectX) { }

                    I++;
                }

                #region Samplings

                Samplings = FSamplings.AsReadOnly();

                Device Device;
                Result = D3D10Functions.CreateDevice(Adapter, DriverType.Hardware, null, 0, Constants.SdkVersion, out Device);
                try
                {
                    if (Result != 0) throw new EDirectX("CreateDevice", Result, "Adapter"); // if no device available, we just skip the adapter
                    
                    // No multisample
                    FSamplings.Add(new SampleDescription { Count = 1, Quality = 0 });

                    uint QualityCount;

                    // Multisample 2x
                    Device.CheckMultisampleQualityLevels(Format.R8G8B8A8_UNorm, 2, out QualityCount);
                    if (QualityCount > 0) FSamplings.Add(new SampleDescription { Count = 2, Quality = 0 });

                    // Multisample 4x
                    Device.CheckMultisampleQualityLevels(Format.R8G8B8A8_UNorm, 4, out QualityCount);
                    if (QualityCount > 0) FSamplings.Add(new SampleDescription { Count = 4, Quality = 0 });
                    if (QualityCount > 4) FSamplings.Add(new SampleDescription { Count = 4, Quality = 4 });

                    // Coveragesample 8x
                    if (QualityCount > 8) FSamplings.Add(new SampleDescription { Count = 4, Quality = 8 });

                    // Coveragesample 16x
                    if (QualityCount > 16) FSamplings.Add(new SampleDescription { Count = 4, Quality = 16 });

                    // Coveragesample 8xQ
                    Device.CheckMultisampleQualityLevels(Format.R8G8B8A8_UNorm, 8, out QualityCount);
                    if (QualityCount > 0) FSamplings.Add(new SampleDescription { Count = 8, Quality = 0 });
                    if (QualityCount > 8) FSamplings.Add(new SampleDescription { Count = 8, Quality = 8 });

                    // Coveragesample 16xQ
                    if (QualityCount > 16) FSamplings.Add(new SampleDescription { Count = 8, Quality = 16 });
                }
                finally
                {
                    if (Device != null) Device.Release();
                }

                #endregion
            }

            internal void Delete()
            {
                foreach (var Monitor in FMonitors)
                {
                    Monitor.Delete();
                }

                Adapter.Release();
                Adapter = null;
            }

            public readonly AdapterDescription Description;

            public readonly ReadOnlyCollection<TMonitor> Monitors;
            public readonly ReadOnlyCollection<SampleDescription> Samplings;
        }

        public static bool Active { get { return Factory != null; } }

        public static readonly ReadOnlyCollection<TAdapter> Adapters = FAdapters.AsReadOnly();

        public static float RationalToFloat(Rational Rational)
        {
            if (Rational.Denominator == 0) return 0;
            return (float)Rational.Numerator / Rational.Denominator;
        }

        public static void Start()
        {
            if (Factory != null) throw new EInvalidCall(Type.Name, Type, ResourcesReason.NotInactive, Type.Name);

            Factory Out;
            var Result = DXGI_Functions.CreateFactory(typeof(Factory), out Out);
            if (Result != 0) throw new ENotAvailable("Direct3D 10", Type.Name);
            Factory = Out;

            try
            {
                var I = 0u;
                Adapter Adapter;
                while (Factory.EnumerateAdapters(I, out Adapter) != (int)DXGI_Error.NotFound)
                {
                    try { FAdapters.Add(new TAdapter(Adapter)); }
                    catch (EDirectX E) { if (E.Source != "CreateDevice") throw; }

                    I++;
                }

                if (FAdapters.Count < 1) throw new ENotAvailable("Direct3D 10 " + ResourcesCommon.Adapter, Type.Name);
            }
            catch
            {
                Stop();
                throw;
            }
        }

        public static void Stop()
        {
            if (Factory == null) throw new EInvalidCall(Type.Name, Type, ResourcesReason.NotActive, Type.Name);

            foreach (var Context in Contexts.Where(Context => Context.Device != null))
            {
                Context.Stop();
            }

            Contexts.Clear();

            foreach (var Adapter in FAdapters)
            {
                Adapter.Delete();
            }
            FAdapters.Clear();

            Factory.Release();
            Factory = null;
        }

        public static string GetResultText(int Result)
        {
            int Out;
            var ResultText = ((DXGI_Error)Result).ToString();
            if (int.TryParse(ResultText, out Out))
            {
                ResultText = ((D3D10Error)Result).ToString();
                if (int.TryParse(ResultText, out Out))
                {
                    ResultText = ((D3DX10Error)Result).ToString();
                    if (int.TryParse(ResultText, out Out))
                    {
                        ResultText = ((Windows.EError)Result).ToString();
                        if (!int.TryParse(ResultText, out Out)) ResultText = typeof(Windows.EError) + "." + ResultText;
                    }
                    else ResultText = typeof(D3DX10Error) + "." + ResultText;
                }
                else ResultText = typeof(D3D10Error) + "." + ResultText;
            }
            else ResultText = typeof(DXGI_Error) + "." + ResultText;

            //var ErrorText = ErrorHandlingFunctions.DXGetErrorString(Result);
            var ErrorDescription = ErrorHandlingFunctions.GetErrorDescription(Result);

            //if (ErrorText != "Unknown") ResultText += " (" + ErrorText + ")";
            if (ErrorDescription != "n/a") ResultText += " : " + ErrorDescription;

            return ResultText;
        }
    }
}